From: Eric Huss Date: Wed, 18 Apr 2018 01:46:44 +0000 (-0700) Subject: Profile Overrides (RFC #2282 Part 1) X-Git-Tag: archive/raspbian/0.35.0-2+rpi1~3^2^2^2^2^2^2^2~22^2~1^2~41^2~22 X-Git-Url: https://dgit.raspbian.org/%22http://www.example.com/cgi/success//%22http:/www.example.com/cgi/success/?a=commitdiff_plain;h=575d6e819cfae17bea30987c9f1ec9509f8220ac;p=cargo.git Profile Overrides (RFC #2282 Part 1) --- diff --git a/src/cargo/core/compiler/context/compilation_files.rs b/src/cargo/core/compiler/context/compilation_files.rs index 88aa1cfc7..f8b0ca5c9 100644 --- a/src/cargo/core/compiler/context/compilation_files.rs +++ b/src/cargo/core/compiler/context/compilation_files.rs @@ -97,7 +97,7 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { /// Returns the appropriate output directory for the specified package and /// target. pub fn out_dir(&self, unit: &Unit<'a>) -> PathBuf { - if unit.profile.doc { + if unit.mode.is_doc() { self.layout(unit.kind).root().parent().unwrap().join("doc") } else if unit.target.is_custom_build() { self.build_script_dir(unit) @@ -148,7 +148,7 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { /// Returns the appropriate directory layout for either a plugin or not. pub fn build_script_dir(&self, unit: &Unit<'a>) -> PathBuf { assert!(unit.target.is_custom_build()); - assert!(!unit.profile.run_custom_build); + assert!(!unit.mode.is_run_custom_build()); let dir = self.pkg_dir(unit); self.layout(Kind::Host).build().join(dir) } @@ -156,7 +156,7 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { /// Returns the appropriate directory layout for either a plugin or not. pub fn build_script_out_dir(&self, unit: &Unit<'a>) -> PathBuf { assert!(unit.target.is_custom_build()); - assert!(unit.profile.run_custom_build); + assert!(unit.mode.is_run_custom_build()); let dir = self.pkg_dir(unit); self.layout(unit.kind).build().join(dir).join("out") } @@ -211,7 +211,7 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { } else { Some(( out_dir.parent().unwrap().to_owned(), - if unit.profile.test { + if unit.mode.is_any_test() { file_stem } else { bin_stem @@ -244,7 +244,10 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { let mut ret = Vec::new(); let mut unsupported = Vec::new(); { - if unit.profile.check { + if unit.mode.is_check() { + // This is not quite correct for non-lib targets. rustc + // currently does not emit rmeta files, so there is nothing to + // check for! See #3624. let path = out_dir.join(format!("lib{}.rmeta", file_stem)); let hardlink = link_stem .clone() @@ -296,7 +299,7 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { | TargetKind::Test => { add("bin", FileFlavor::Normal)?; } - TargetKind::Lib(..) | TargetKind::ExampleLib(..) if unit.profile.test => { + TargetKind::Lib(..) | TargetKind::ExampleLib(..) if unit.mode.is_any_test() => { add("bin", FileFlavor::Normal)?; } TargetKind::ExampleLib(ref kinds) | TargetKind::Lib(ref kinds) => { @@ -378,7 +381,7 @@ fn compute_metadata<'a, 'cfg>( // just here for rustbuild. We need a more principled method // doing this eventually. let __cargo_default_lib_metadata = env::var("__CARGO_DEFAULT_LIB_METADATA"); - if !(unit.profile.test || unit.profile.check) + if !(unit.mode.is_any_test() || unit.mode.is_check()) && (unit.target.is_dylib() || unit.target.is_cdylib() || (unit.target.is_bin() && cx.build_config.target_triple().starts_with("wasm32-"))) && unit.pkg.package_id().source_id().is_path() @@ -422,7 +425,8 @@ fn compute_metadata<'a, 'cfg>( // Throw in the profile we're compiling with. This helps caching // panic=abort and panic=unwind artifacts, additionally with various // settings like debuginfo and whatnot. - unit.profile.hash(&mut hasher); + cx.unit_profile(unit).hash(&mut hasher); + unit.mode.hash(&mut hasher); // Artifacts compiled for the host should have a different metadata // piece than those compiled for the target, so make sure we throw in diff --git a/src/cargo/core/compiler/context/mod.rs b/src/cargo/core/compiler/context/mod.rs index fdf47780b..023804989 100644 --- a/src/cargo/core/compiler/context/mod.rs +++ b/src/cargo/core/compiler/context/mod.rs @@ -1,5 +1,4 @@ #![allow(deprecated)] - use std::collections::{HashMap, HashSet}; use std::env; use std::path::{Path, PathBuf}; @@ -8,8 +7,10 @@ use std::sync::Arc; use jobserver::Client; -use core::{Package, PackageId, PackageSet, Profile, Resolve, Target}; -use core::{Dependency, Profiles, Workspace}; +use core::{Package, PackageId, PackageSet, Resolve, Target}; +use core::{Dependency, Workspace}; +use core::profiles::{Profile, ProfileFor, Profiles}; +use ops::CompileMode; use util::{internal, profile, Cfg, CfgExpr, Config}; use util::errors::{CargoResult, CargoResultExt}; @@ -45,7 +46,7 @@ pub use self::target_info::{FileFlavor, TargetInfo}; /// example, it needs to know the target architecture (OS, chip arch etc.) and it needs to know /// whether you want a debug or release build. There is enough information in this struct to figure /// all that out. -#[derive(Clone, Copy, Eq, PartialEq, Hash)] +#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)] pub struct Unit<'a> { /// Information about available targets, which files to include/exclude, etc. Basically stuff in /// `Cargo.toml`. @@ -54,9 +55,9 @@ pub struct Unit<'a> { /// to be confused with *target-triple* (or *target architecture* ...), the target arch for a /// build. pub target: &'a Target, - /// The profile contains information about *how* the build should be run, including debug - /// level, extra args to pass to rustc, etc. - pub profile: &'a Profile, + /// This indicates the purpose of the target for profile selection. See + /// `ProfileFor` for more details. + pub profile_for: ProfileFor, /// Whether this compilation unit is for the host or target architecture. /// /// For example, when @@ -64,6 +65,9 @@ pub struct Unit<'a> { /// the host architecture so the host rustc can use it (when compiling to the target /// architecture). pub kind: Kind, + /// The "mode" this unit is being compiled for. See `CompileMode` for + /// more details. + pub mode: CompileMode, } /// The build context, containing all information about a build task @@ -87,13 +91,19 @@ pub struct Context<'a, 'cfg: 'a> { pub links: Links<'a>, pub used_in_plugin: HashSet>, pub jobserver: Client, + pub profiles: &'a Profiles, + /// This is a workaround to carry the extra compiler args given on the + /// command-line for `cargo rustc` and `cargo rustdoc`. These commands + /// only support one target, but we don't want the args passed to any + /// dependencies. + pub extra_compiler_args: HashMap, Vec>, target_info: TargetInfo, host_info: TargetInfo, - profiles: &'a Profiles, incremental_env: Option, unit_dependencies: HashMap, Vec>>, + unit_profiles: HashMap, Profile>, files: Option>, } @@ -105,6 +115,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> { config: &'cfg Config, build_config: BuildConfig, profiles: &'a Profiles, + extra_compiler_args: HashMap, Vec>, ) -> CargoResult> { let incremental_env = match env::var("CARGO_INCREMENTAL") { Ok(v) => Some(v == "1"), @@ -155,7 +166,9 @@ impl<'a, 'cfg> Context<'a, 'cfg> { build_script_overridden: HashSet::new(), unit_dependencies: HashMap::new(), + unit_profiles: HashMap::new(), files: None, + extra_compiler_args, }; cx.compilation.host_dylib_path = cx.host_info.sysroot_libdir.clone(); @@ -174,8 +187,8 @@ impl<'a, 'cfg> Context<'a, 'cfg> { let mut queue = JobQueue::new(&self); self.prepare_units(export_dir, units)?; self.prepare()?; - self.build_used_in_plugin_map(&units)?; - custom_build::build_map(&mut self, &units)?; + self.build_used_in_plugin_map(units)?; + custom_build::build_map(&mut self, units)?; for unit in units.iter() { // Build up a list of pending jobs, each of which represent @@ -200,7 +213,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> { None => &output.path, }; - if unit.profile.test { + if unit.mode.is_any_test() && !unit.mode.is_check() { self.compilation.tests.push(( unit.pkg.clone(), unit.target.kind().clone(), @@ -224,7 +237,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> { continue; } - if dep.profile.run_custom_build { + if dep.mode.is_run_custom_build() { let out_dir = self.files().build_script_out_dir(dep).display().to_string(); self.compilation .extra_env @@ -236,7 +249,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> { if !dep.target.is_lib() { continue; } - if dep.profile.doc { + if dep.mode.is_doc() { continue; } @@ -313,15 +326,16 @@ impl<'a, 'cfg> Context<'a, 'cfg> { None => None, }; - let deps = build_unit_dependencies(units, &self)?; + let deps = build_unit_dependencies(units, self)?; self.unit_dependencies = deps; + self.unit_profiles = self.profiles.build_unit_profiles(units, self); let files = CompilationFiles::new( units, host_layout, target_layout, export_dir, self.ws, - &self, + self, ); self.files = Some(files); Ok(()) @@ -414,7 +428,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> { // dependencies. However, that code itself calls this method and // gets a full pre-filtered set of dependencies. This is not super // obvious, and clear, but it does work at the moment. - if unit.profile.run_custom_build { + if unit.target.is_custom_build() { let key = (unit.pkg.package_id().clone(), unit.kind); if self.build_script_overridden.contains(&key) { return Vec::new(); @@ -476,26 +490,11 @@ impl<'a, 'cfg> Context<'a, 'cfg> { self.build_config.jobs } - pub fn lib_profile(&self) -> &'a Profile { - let (normal, test) = if self.build_config.release { - (&self.profiles.release, &self.profiles.bench_deps) - } else { - (&self.profiles.dev, &self.profiles.test_deps) - }; - if self.build_config.test { - test - } else { - normal - } - } - - pub fn build_script_profile(&self, _pkg: &PackageId) -> &'a Profile { - // TODO: should build scripts always be built with the same library - // profile? How is this controlled at the CLI layer? - self.lib_profile() - } - - pub fn incremental_args(&self, unit: &Unit) -> CargoResult> { + pub fn incremental_args( + &self, + unit: &Unit, + profile_incremental: bool, + ) -> CargoResult> { // There's a number of ways to configure incremental compilation right // now. In order of descending priority (first is highest priority) we // have: @@ -518,7 +517,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> { // have it enabled by default while release profiles have it disabled // by default. let global_cfg = self.config.get_bool("build.incremental")?.map(|c| c.val); - let incremental = match (self.incremental_env, global_cfg, unit.profile.incremental) { + let incremental = match (self.incremental_env, global_cfg, profile_incremental) { (Some(v), _, _) => v, (None, Some(false), _) => false, (None, _, other) => other, @@ -572,6 +571,13 @@ impl<'a, 'cfg> Context<'a, 'cfg> { Kind::Target => &self.target_info, } } + + /// Returns the profile for a given unit. + /// This should not be called until profiles are computed in + /// `prepare_units`. + pub fn unit_profile(&self, unit: &Unit<'a>) -> &Profile { + &self.unit_profiles[unit] + } } /// Acquire extra flags to pass to the compiler from various locations. diff --git a/src/cargo/core/compiler/context/unit_dependencies.rs b/src/cargo/core/compiler/context/unit_dependencies.rs index b65a55f15..7460f13ca 100644 --- a/src/cargo/core/compiler/context/unit_dependencies.rs +++ b/src/cargo/core/compiler/context/unit_dependencies.rs @@ -16,11 +16,12 @@ //! graph of `Unit`s, which capture these properties. use super::{Context, Kind, Unit}; -use std::collections::HashMap; -use CargoResult; use core::dependency::Kind as DepKind; +use core::profiles::ProfileFor; use core::Target; -use core::Profile; +use ops::CompileMode; +use std::collections::HashMap; +use CargoResult; pub fn build_unit_dependencies<'a, 'cfg>( roots: &[Unit<'a>], @@ -28,7 +29,7 @@ pub fn build_unit_dependencies<'a, 'cfg>( ) -> CargoResult, Vec>>> { let mut deps = HashMap::new(); for unit in roots.iter() { - deps_of(unit, cx, &mut deps)?; + deps_of(unit, cx, &mut deps, unit.profile_for)?; } Ok(deps) @@ -38,12 +39,14 @@ fn deps_of<'a, 'b, 'cfg>( unit: &Unit<'a>, cx: &Context<'a, 'cfg>, deps: &'b mut HashMap, Vec>>, + profile_for: ProfileFor, ) -> CargoResult<&'b [Unit<'a>]> { if !deps.contains_key(unit) { - let unit_deps = compute_deps(unit, cx, deps)?; - deps.insert(*unit, unit_deps.clone()); - for unit in unit_deps { - deps_of(&unit, cx, deps)?; + let unit_deps = compute_deps(unit, cx, deps, profile_for)?; + let to_insert: Vec<_> = unit_deps.iter().map(|&(unit, _)| unit).collect(); + deps.insert(*unit, to_insert); + for (unit, profile_for) in unit_deps { + deps_of(&unit, cx, deps, profile_for)?; } } Ok(deps[unit].as_ref()) @@ -51,68 +54,72 @@ fn deps_of<'a, 'b, 'cfg>( /// For a package, return all targets which are registered as dependencies /// for that package. +/// This returns a vec of `(Unit, ProfileFor)` pairs. The `ProfileFor` +/// is the profile type that should be used for dependencies of the unit. fn compute_deps<'a, 'b, 'cfg>( unit: &Unit<'a>, cx: &Context<'a, 'cfg>, deps: &'b mut HashMap, Vec>>, -) -> CargoResult>> { - if unit.profile.run_custom_build { + profile_for: ProfileFor, +) -> CargoResult, ProfileFor)>> { + if unit.mode.is_run_custom_build() { return compute_deps_custom_build(unit, cx, deps); - } else if unit.profile.doc && !unit.profile.test { + } else if unit.mode.is_doc() && !unit.mode.is_any_test() { + // Note: This does not include Doctest. return compute_deps_doc(unit, cx); } let id = unit.pkg.package_id(); let deps = cx.resolve.deps(id); - let mut ret = deps - .filter(|&(_id, deps)| { - assert!(deps.len() > 0); - deps.iter().any(|dep| { - // If this target is a build command, then we only want build - // dependencies, otherwise we want everything *other than* build - // dependencies. - if unit.target.is_custom_build() != dep.is_build() { - return false; - } + let mut ret = deps.filter(|&(_id, deps)| { + assert!(deps.len() > 0); + deps.iter().any(|dep| { + // If this target is a build command, then we only want build + // dependencies, otherwise we want everything *other than* build + // dependencies. + if unit.target.is_custom_build() != dep.is_build() { + return false; + } - // If this dependency is *not* a transitive dependency, then it - // only applies to test/example targets - if !dep.is_transitive() && !unit.target.is_test() && !unit.target.is_example() - && !unit.profile.test - { - return false; - } + // If this dependency is *not* a transitive dependency, then it + // only applies to test/example targets + if !dep.is_transitive() && !unit.target.is_test() && !unit.target.is_example() + && !unit.mode.is_any_test() + { + return false; + } - // If this dependency is only available for certain platforms, - // make sure we're only enabling it for that platform. - if !cx.dep_platform_activated(dep, unit.kind) { - return false; - } + // If this dependency is only available for certain platforms, + // make sure we're only enabling it for that platform. + if !cx.dep_platform_activated(dep, unit.kind) { + return false; + } - // If the dependency is optional, then we're only activating it - // if the corresponding feature was activated - if dep.is_optional() && !cx.resolve.features(id).contains(&*dep.name()) { - return false; - } + // If the dependency is optional, then we're only activating it + // if the corresponding feature was activated + if dep.is_optional() && !cx.resolve.features(id).contains(&*dep.name()) { + return false; + } - // If we've gotten past all that, then this dependency is - // actually used! - true - }) + // If we've gotten past all that, then this dependency is + // actually used! + true }) - .filter_map(|(id, _)| { - match cx.get_package(id) { - Ok(pkg) => pkg.targets().iter().find(|t| t.is_lib()).map(|t| { - let unit = Unit { + }).filter_map(|(id, _)| match cx.get_package(id) { + Ok(pkg) => pkg.targets().iter().find(|t| t.is_lib()).map(|t| { + let mode = check_or_build_mode(&unit.mode, t); + Ok(( + Unit { pkg, target: t, - profile: lib_or_check_profile(unit, t, cx), + profile_for, kind: unit.kind.for_target(t), - }; - Ok(unit) - }), - Err(e) => Some(Err(e)), - } + mode, + }, + profile_for, + )) + }), + Err(e) => Some(Err(e)), }) .collect::>>()?; @@ -122,40 +129,17 @@ fn compute_deps<'a, 'b, 'cfg>( if unit.target.is_custom_build() { return Ok(ret); } - ret.extend(dep_build_script(unit, cx)); + ret.extend(dep_build_script(unit)); // If this target is a binary, test, example, etc, then it depends on // the library of the same package. The call to `resolve.deps` above // didn't include `pkg` in the return values, so we need to special case // it here and see if we need to push `(pkg, pkg_lib_target)`. - if unit.target.is_lib() && !unit.profile.doc { + if unit.target.is_lib() && unit.mode != CompileMode::Doctest { return Ok(ret); } - ret.extend(maybe_lib(unit, cx)); - - // Integration tests/benchmarks require binaries to be built - if unit.profile.test && (unit.target.is_test() || unit.target.is_bench()) { - ret.extend( - unit.pkg - .targets() - .iter() - .filter(|t| { - let no_required_features = Vec::new(); + ret.extend(maybe_lib(unit, profile_for)); - t.is_bin() && - // Skip binaries with required features that have not been selected. - t.required_features().unwrap_or(&no_required_features).iter().all(|f| { - cx.resolve.features(id).contains(f) - }) - }) - .map(|t| Unit { - pkg: unit.pkg, - target: t, - profile: lib_or_check_profile(unit, t, cx), - kind: unit.kind.for_target(t), - }), - ); - } Ok(ret) } @@ -167,10 +151,10 @@ fn compute_deps_custom_build<'a, 'cfg>( unit: &Unit<'a>, cx: &Context<'a, 'cfg>, deps: &mut HashMap, Vec>>, -) -> CargoResult>> { +) -> CargoResult, ProfileFor)>> { // When not overridden, then the dependencies to run a build script are: // - // 1. Compiling the build script itcx + // 1. Compiling the build script itself // 2. For each immediate dependency of our package which has a `links` // key, the execution of that build script. let not_custom_build = unit.pkg @@ -179,23 +163,32 @@ fn compute_deps_custom_build<'a, 'cfg>( .find(|t| !t.is_custom_build()) .unwrap(); let tmp = Unit { + pkg: unit.pkg, target: not_custom_build, - profile: &cx.profiles.dev, - ..*unit + // The profile here isn't critical. We are just using this temp unit + // for fetching dependencies that might have `links`. + profile_for: ProfileFor::Any, + kind: unit.kind, + mode: CompileMode::Build, }; - let deps = deps_of(&tmp, cx, deps)?; + let deps = deps_of(&tmp, cx, deps, ProfileFor::CustomBuild)?; Ok(deps.iter() .filter_map(|unit| { if !unit.target.linkable() || unit.pkg.manifest().links().is_none() { return None; } - dep_build_script(unit, cx) + dep_build_script(unit) }) - .chain(Some(Unit { - profile: cx.build_script_profile(unit.pkg.package_id()), - kind: Kind::Host, // build scripts always compiled for the host - ..*unit - })) + .chain(Some(( + Unit { + pkg: unit.pkg, + target: unit.target, + profile_for: ProfileFor::CustomBuild, + kind: Kind::Host, // build scripts always compiled for the host + mode: CompileMode::Build, + }, + ProfileFor::CustomBuild, + ))) .collect()) } @@ -203,15 +196,13 @@ fn compute_deps_custom_build<'a, 'cfg>( fn compute_deps_doc<'a, 'cfg>( unit: &Unit<'a>, cx: &Context<'a, 'cfg>, -) -> CargoResult>> { +) -> CargoResult, ProfileFor)>> { let deps = cx.resolve .deps(unit.pkg.package_id()) .filter(|&(_id, deps)| { - deps.iter().any(|dep| { - match dep.kind() { - DepKind::Normal => cx.dep_platform_activated(dep, unit.kind), - _ => false, - } + deps.iter().any(|dep| match dep.kind() { + DepKind::Normal => cx.dep_platform_activated(dep, unit.kind), + _ => false, }) }) .map(|(id, _deps)| cx.get_package(id)); @@ -226,43 +217,57 @@ fn compute_deps_doc<'a, 'cfg>( Some(lib) => lib, None => continue, }; - ret.push(Unit { - pkg: dep, - target: lib, - profile: lib_or_check_profile(unit, lib, cx), - kind: unit.kind.for_target(lib), - }); - if cx.build_config.doc_all { - ret.push(Unit { + // rustdoc only needs rmeta files for regular dependencies. + // However, for plugins/proc-macros, deps should be built like normal. + let mode = check_or_build_mode(&unit.mode, lib); + ret.push(( + Unit { pkg: dep, target: lib, - profile: &cx.profiles.doc, + profile_for: ProfileFor::Any, kind: unit.kind.for_target(lib), - }); + mode, + }, + ProfileFor::Any, + )); + if let CompileMode::Doc { deps: true } = unit.mode { + ret.push(( + Unit { + pkg: dep, + target: lib, + profile_for: ProfileFor::Any, + kind: unit.kind.for_target(lib), + mode: unit.mode, + }, + ProfileFor::Any, + )); } } // Be sure to build/run the build script for documented libraries as - ret.extend(dep_build_script(unit, cx)); + ret.extend(dep_build_script(unit)); // If we document a binary, we need the library available if unit.target.is_bin() { - ret.extend(maybe_lib(unit, cx)); + ret.extend(maybe_lib(unit, ProfileFor::Any)); } Ok(ret) } -fn maybe_lib<'a, 'cfg>(unit: &Unit<'a>, cx: &Context<'a, 'cfg>) -> Option> { - unit.pkg - .targets() - .iter() - .find(|t| t.linkable()) - .map(|t| Unit { - pkg: unit.pkg, - target: t, - profile: lib_or_check_profile(unit, t, cx), - kind: unit.kind.for_target(t), - }) +fn maybe_lib<'a>(unit: &Unit<'a>, profile_for: ProfileFor) -> Option<(Unit<'a>, ProfileFor)> { + let mode = check_or_build_mode(&unit.mode, unit.target); + unit.pkg.targets().iter().find(|t| t.linkable()).map(|t| { + ( + Unit { + pkg: unit.pkg, + target: t, + profile_for, + kind: unit.kind.for_target(t), + mode, + }, + profile_for, + ) + }) } /// If a build script is scheduled to be run for the package specified by @@ -272,28 +277,44 @@ fn maybe_lib<'a, 'cfg>(unit: &Unit<'a>, cx: &Context<'a, 'cfg>) -> Option(unit: &Unit<'a>, cx: &Context<'a, 'cfg>) -> Option> { +fn dep_build_script<'a>(unit: &Unit<'a>) -> Option<(Unit<'a>, ProfileFor)> { unit.pkg .targets() .iter() .find(|t| t.is_custom_build()) - .map(|t| Unit { - pkg: unit.pkg, - target: t, - profile: &cx.profiles.custom_build, - kind: unit.kind, + .map(|t| { + ( + Unit { + pkg: unit.pkg, + target: t, + // The profile for *running* the build script will actually be the + // target the build script is running for (so that the environment + // variables get set correctly). This is overridden in + // `Profiles::build_unit_profiles`, so the exact value here isn't + // critical. + profile_for: ProfileFor::CustomBuild, + kind: unit.kind, + mode: CompileMode::RunCustomBuild, + }, + ProfileFor::CustomBuild, + ) }) } -fn lib_or_check_profile<'a, 'cfg>( - unit: &Unit, - target: &Target, - cx: &Context<'a, 'cfg>, -) -> &'a Profile { - if !target.is_custom_build() && !target.for_host() - && (unit.profile.check || (unit.profile.doc && !unit.profile.test)) - { - return &cx.profiles.check; +/// Choose the correct mode for dependencies. +fn check_or_build_mode(mode: &CompileMode, target: &Target) -> CompileMode { + match *mode { + CompileMode::Check { .. } | CompileMode::Doc { .. } => { + if target.for_host() { + // Plugin and proc-macro targets should be compiled like + // normal. + CompileMode::Build + } else { + // Regular dependencies should not be checked with --test. + // Regular dependencies of doc targets should emit rmeta only. + CompileMode::Check { test: false } + } + } + _ => CompileMode::Build, } - cx.lib_profile() } diff --git a/src/cargo/core/compiler/custom_build.rs b/src/cargo/core/compiler/custom_build.rs index 320af6403..3b00c478c 100644 --- a/src/cargo/core/compiler/custom_build.rs +++ b/src/cargo/core/compiler/custom_build.rs @@ -102,11 +102,11 @@ pub fn prepare<'a, 'cfg>( } fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult<(Work, Work)> { - assert!(unit.profile.run_custom_build); + assert!(unit.mode.is_run_custom_build()); let dependencies = cx.dep_targets(unit); let build_script_unit = dependencies .iter() - .find(|d| !d.profile.run_custom_build && d.target.is_custom_build()) + .find(|d| !d.mode.is_run_custom_build() && d.target.is_custom_build()) .expect("running a script not depending on an actual script"); let script_output = cx.files().build_script_dir(build_script_unit); let build_output = cx.files().build_script_out_dir(unit); @@ -118,9 +118,9 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes // environment variables. Note that the profile-related environment // variables are not set with this the build script's profile but rather the // package's library profile. - let profile = cx.lib_profile(); let to_exec = to_exec.into_os_string(); let mut cmd = cx.compilation.host_process(to_exec, unit.pkg)?; + let profile = cx.unit_profile(unit).clone(); cmd.env("OUT_DIR", &build_output) .env("CARGO_MANIFEST_DIR", unit.pkg.root()) .env("NUM_JOBS", &cx.jobs().to_string()) @@ -132,7 +132,7 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes }, ) .env("DEBUG", &profile.debuginfo.is_some().to_string()) - .env("OPT_LEVEL", &profile.opt_level) + .env("OPT_LEVEL", &profile.opt_level.to_string()) .env( "PROFILE", if cx.build_config.release { @@ -196,7 +196,7 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes dependencies .iter() .filter_map(|unit| { - if unit.profile.run_custom_build { + if unit.mode.is_run_custom_build() { Some(( unit.pkg.manifest().links().unwrap().to_string(), unit.pkg.package_id().clone(), diff --git a/src/cargo/core/compiler/fingerprint.rs b/src/cargo/core/compiler/fingerprint.rs index 9d3fc4d50..100912ceb 100644 --- a/src/cargo/core/compiler/fingerprint.rs +++ b/src/cargo/core/compiler/fingerprint.rs @@ -86,7 +86,7 @@ pub fn prepare_target<'a, 'cfg>( let root = cx.files().out_dir(unit); let mut missing_outputs = false; - if unit.profile.doc { + if unit.mode.is_doc() { missing_outputs = !root.join(unit.target.crate_name()) .join("index.html") .exists(); @@ -102,7 +102,7 @@ pub fn prepare_target<'a, 'cfg>( } } - let allow_failure = unit.profile.rustc_args.is_some(); + let allow_failure = cx.extra_compiler_args.get(unit).is_some(); let target_root = cx.files().target_root().to_path_buf(); let write_fingerprint = Work::new(move |_| { match fingerprint.update_local(&target_root) { @@ -449,15 +449,23 @@ fn calculate<'a, 'cfg>( }; let mut deps = deps; deps.sort_by(|&(ref a, _), &(ref b, _)| a.cmp(b)); - let extra_flags = if unit.profile.doc { + let extra_flags = if unit.mode.is_doc() { cx.rustdocflags_args(unit)? } else { cx.rustflags_args(unit)? }; + let profile_hash = { + let profile = cx.unit_profile(unit); + util::hash_u64(&( + profile, + unit.mode, + cx.incremental_args(unit, profile.incremental)?, + )) + }; let fingerprint = Arc::new(Fingerprint { rustc: util::hash_u64(&cx.build_config.rustc.verbose_version), target: util::hash_u64(&unit.target), - profile: util::hash_u64(&(&unit.profile, cx.incremental_args(unit)?)), + profile: profile_hash, // Note that .0 is hashed here, not .1 which is the cwd. That doesn't // actually affect the output artifact so there's no need to hash it. path: util::hash_u64(&super::path_args(cx, unit).0), @@ -478,7 +486,7 @@ fn calculate<'a, 'cfg>( // responsibility of the source) fn use_dep_info(unit: &Unit) -> bool { let path = unit.pkg.summary().source_id().is_path(); - !unit.profile.doc && path + !unit.mode.is_doc() && path } /// Prepare the necessary work for the fingerprint of a build command. @@ -758,9 +766,9 @@ fn filename<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> String { TargetKind::Bench => "bench", TargetKind::CustomBuild => "build-script", }; - let flavor = if unit.profile.test { + let flavor = if unit.mode.is_any_test() && !unit.mode.is_check() { "test-" - } else if unit.profile.doc { + } else if unit.mode.is_doc() { "doc-" } else { "" diff --git a/src/cargo/core/compiler/job_queue.rs b/src/cargo/core/compiler/job_queue.rs index 9f33e9ca4..4fcaf4ea5 100644 --- a/src/cargo/core/compiler/job_queue.rs +++ b/src/cargo/core/compiler/job_queue.rs @@ -8,7 +8,9 @@ use std::sync::mpsc::{channel, Receiver, Sender}; use crossbeam::{self, Scope}; use jobserver::{Acquired, HelperThread}; -use core::{PackageId, Profile, Target}; +use core::{PackageId, Target}; +use core::profiles::ProfileFor; +use ops::CompileMode; use util::{Config, DependencyQueue, Dirty, Fresh, Freshness}; use util::{internal, profile, CargoResult, CargoResultExt, ProcessBuilder}; use handle_error; @@ -46,8 +48,9 @@ struct PendingBuild { struct Key<'a> { pkg: &'a PackageId, target: &'a Target, - profile: &'a Profile, + profile_for: ProfileFor, kind: Kind, + mode: CompileMode, } pub struct JobState<'a> { @@ -231,7 +234,7 @@ impl<'a> JobQueue<'a> { Ok(()) => self.finish(key, cx)?, Err(e) => { let msg = "The following warnings were emitted during compilation:"; - self.emit_warnings(Some(msg), key, cx)?; + self.emit_warnings(Some(msg), &key, cx)?; if self.active > 0 { error = Some(format_err!("build failed")); @@ -253,8 +256,9 @@ impl<'a> JobQueue<'a> { } let build_type = if self.is_release { "release" } else { "dev" }; - let profile = cx.lib_profile(); - let mut opt_type = String::from(if profile.opt_level == "0" { + // TODO FIXME: We don't know which pkg to display this for! + let profile = cx.profiles.base_profile(self.is_release); + let mut opt_type = String::from(if profile.opt_level.as_str() == "0" { "unoptimized" } else { "optimized" @@ -316,7 +320,7 @@ impl<'a> JobQueue<'a> { Ok(()) } - fn emit_warnings(&self, msg: Option<&str>, key: Key<'a>, cx: &mut Context) -> CargoResult<()> { + fn emit_warnings(&self, msg: Option<&str>, key: &Key<'a>, cx: &mut Context) -> CargoResult<()> { let output = cx.build_state.outputs.lock().unwrap(); if let Some(output) = output.get(&(key.pkg.clone(), key.kind)) { if let Some(msg) = msg { @@ -339,8 +343,8 @@ impl<'a> JobQueue<'a> { } fn finish(&mut self, key: Key<'a>, cx: &mut Context) -> CargoResult<()> { - if key.profile.run_custom_build && cx.show_warnings(key.pkg) { - self.emit_warnings(None, key, cx)?; + if key.mode.is_run_custom_build() && cx.show_warnings(key.pkg) { + self.emit_warnings(None, &key, cx)?; } let state = self.pending.get_mut(&key).unwrap(); @@ -366,8 +370,8 @@ impl<'a> JobQueue<'a> { key: &Key<'a>, fresh: Freshness, ) -> CargoResult<()> { - if (self.compiled.contains(key.pkg) && !key.profile.doc) - || (self.documented.contains(key.pkg) && key.profile.doc) + if (self.compiled.contains(key.pkg) && !key.mode.is_doc()) + || (self.documented.contains(key.pkg) && key.mode.is_doc()) { return Ok(()); } @@ -376,14 +380,15 @@ impl<'a> JobQueue<'a> { // Any dirty stage which runs at least one command gets printed as // being a compiled package Dirty => { - if key.profile.doc { - if !key.profile.test { + if key.mode.is_doc() { + // Skip Doctest + if !key.mode.is_any_test() { self.documented.insert(key.pkg); config.shell().status("Documenting", key.pkg)?; } } else { self.compiled.insert(key.pkg); - if key.profile.check { + if key.mode.is_check() { config.shell().status("Checking", key.pkg)?; } else { config.shell().status("Compiling", key.pkg)?; @@ -405,8 +410,9 @@ impl<'a> Key<'a> { Key { pkg: unit.pkg.package_id(), target: unit.target, - profile: unit.profile, + profile_for: unit.profile_for, kind: unit.kind, + mode: unit.mode, } } @@ -414,8 +420,9 @@ impl<'a> Key<'a> { let unit = Unit { pkg: cx.get_package(self.pkg)?, target: self.target, - profile: self.profile, + profile_for: self.profile_for, kind: self.kind, + mode: self.mode, }; let targets = cx.dep_targets(&unit); Ok(targets @@ -437,8 +444,8 @@ impl<'a> fmt::Debug for Key<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, - "{} => {}/{} => {:?}", - self.pkg, self.target, self.profile, self.kind + "{} => {}/{:?} => {:?}", + self.pkg, self.target, self.mode, self.kind ) } } diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index fccdfb6c2..05c31e96a 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -9,9 +9,10 @@ use std::sync::Arc; use same_file::is_same_file; use serde_json; -use core::{Feature, PackageId, Profile, Target}; -use core::manifest::Lto; +use core::{Feature, PackageId, Target}; +use core::profiles::{Lto, Profile}; use core::shell::ColorChoice; +use ops::CompileMode; use util::{self, machine_message, Config, Freshness, ProcessBuilder, Rustc}; use util::{internal, join_paths, profile}; use util::paths; @@ -59,10 +60,6 @@ pub struct BuildConfig { pub jobs: u32, /// Whether we are building for release pub release: bool, - /// Whether we are running tests - pub test: bool, - /// Whether we are building documentation - pub doc_all: bool, /// Whether to print std output in json format (for machine reading) pub json_messages: bool, } @@ -131,8 +128,6 @@ impl BuildConfig { host: host_config, target: target_config, release: false, - test: false, - doc_all: false, json_messages: false, }) } @@ -304,14 +299,14 @@ fn compile<'a, 'cfg: 'a>( fingerprint::prepare_init(cx, unit)?; cx.links.validate(cx.resolve, unit)?; - let (dirty, fresh, freshness) = if unit.profile.run_custom_build { + let (dirty, fresh, freshness) = if unit.mode.is_run_custom_build() { custom_build::prepare(cx, unit)? - } else if unit.profile.doc && unit.profile.test { + } else if unit.mode == CompileMode::Doctest { // we run these targets later, so this is just a noop for now (Work::noop(), Work::noop(), Freshness::Fresh) } else { let (mut freshness, dirty, fresh) = fingerprint::prepare_target(cx, unit)?; - let work = if unit.profile.doc { + let work = if unit.mode.is_doc() { rustdoc(cx, unit)? } else { rustc(cx, unit, exec)? @@ -369,7 +364,7 @@ fn rustc<'a, 'cfg>( // If we are a binary and the package also contains a library, then we // don't pass the `-l` flags. let pass_l_flag = unit.target.is_lib() || !unit.pkg.targets().iter().any(|t| t.is_lib()); - let do_rename = unit.target.allows_underscores() && !unit.profile.test; + let do_rename = unit.target.allows_underscores() && !unit.mode.is_any_test(); let real_name = unit.target.name().to_string(); let crate_name = unit.target.crate_name(); @@ -561,7 +556,8 @@ fn link_targets<'a, 'cfg>( let export_dir = cx.files().export_dir(unit); let package_id = unit.pkg.package_id().clone(); let target = unit.target.clone(); - let profile = unit.profile.clone(); + let profile = cx.unit_profile(unit).clone(); + let unit_mode = unit.mode; let features = cx.resolve .features_sorted(&package_id) .into_iter() @@ -601,10 +597,18 @@ fn link_targets<'a, 'cfg>( } if json_messages { + let art_profile = machine_message::ArtifactProfile { + opt_level: profile.opt_level.as_str(), + debuginfo: profile.debuginfo, + debug_assertions: profile.debug_assertions, + overflow_checks: profile.overflow_checks, + test: unit_mode.is_any_test(), + }; + machine_message::emit(&machine_message::Artifact { package_id: &package_id, target: &target, - profile: &profile, + profile: art_profile, features, filenames: destinations, fresh, @@ -770,7 +774,7 @@ fn rustdoc<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult rustdoc.arg(format!("--edition={}", &manifest.edition())); } - if let Some(ref args) = unit.profile.rustdoc_args { + if let Some(args) = cx.extra_compiler_args.get(unit) { rustdoc.args(args); } @@ -835,23 +839,21 @@ fn build_base_args<'a, 'cfg>( unit: &Unit<'a>, crate_types: &[&str], ) -> CargoResult<()> { + assert!(!unit.mode.is_run_custom_build()); + let Profile { ref opt_level, ref lto, codegen_units, - ref rustc_args, debuginfo, debug_assertions, overflow_checks, rpath, - test, - doc: _doc, - run_custom_build, ref panic, - check, + incremental, .. - } = *unit.profile; - assert!(!run_custom_build); + } = *cx.unit_profile(unit); + let test = unit.mode.is_any_test(); cmd.arg("--crate-name").arg(&unit.target.crate_name()); @@ -877,7 +879,7 @@ fn build_base_args<'a, 'cfg>( } } - if check { + if unit.mode.is_check() { cmd.arg("--emit=dep-info,metadata"); } else { cmd.arg("--emit=dep-info,link"); @@ -889,7 +891,7 @@ fn build_base_args<'a, 'cfg>( cmd.arg("-C").arg("prefer-dynamic"); } - if opt_level != "0" { + if opt_level.as_str() != "0" { cmd.arg("-C").arg(&format!("opt-level={}", opt_level)); } @@ -938,14 +940,14 @@ fn build_base_args<'a, 'cfg>( cmd.arg("-C").arg(format!("debuginfo={}", debuginfo)); } - if let Some(ref args) = *rustc_args { + if let Some(args) = cx.extra_compiler_args.get(unit) { cmd.args(args); } // -C overflow-checks is implied by the setting of -C debug-assertions, // so we only need to provide -C overflow-checks if it differs from // the value of -C debug-assertions we would provide. - if opt_level != "0" { + if opt_level.as_str() != "0" { if debug_assertions { cmd.args(&["-C", "debug-assertions=on"]); if !overflow_checks { @@ -1020,7 +1022,7 @@ fn build_base_args<'a, 'cfg>( "linker=", cx.linker(unit.kind).map(|s| s.as_ref()), ); - cmd.args(&cx.incremental_args(unit)?); + cmd.args(&cx.incremental_args(unit, incremental)?); Ok(()) } @@ -1053,11 +1055,11 @@ fn build_deps_args<'a, 'cfg>( // error in the future, see PR #4797 if !dep_targets .iter() - .any(|u| !u.profile.doc && u.target.linkable()) + .any(|u| !u.mode.is_doc() && u.target.linkable()) { if let Some(u) = dep_targets .iter() - .find(|u| !u.profile.doc && u.target.is_lib()) + .find(|u| !u.mode.is_doc() && u.target.is_lib()) { cx.config.shell().warn(format!( "The package `{}` \ @@ -1072,10 +1074,10 @@ fn build_deps_args<'a, 'cfg>( } for dep in dep_targets { - if dep.profile.run_custom_build { + if dep.mode.is_run_custom_build() { cmd.env("OUT_DIR", &cx.files().build_script_out_dir(&dep)); } - if dep.target.linkable() && !dep.profile.doc { + if dep.target.linkable() && !dep.mode.is_doc() { link_to(cmd, cx, unit, &dep)?; } } diff --git a/src/cargo/core/compiler/output_depinfo.rs b/src/cargo/core/compiler/output_depinfo.rs index dcdbb95f8..242222ef3 100644 --- a/src/cargo/core/compiler/output_depinfo.rs +++ b/src/cargo/core/compiler/output_depinfo.rs @@ -34,7 +34,7 @@ fn add_deps_for_unit<'a, 'b>( // units representing the execution of a build script don't actually // generate a dep info file, so we just keep on going below - if !unit.profile.run_custom_build { + if !unit.mode.is_run_custom_build() { // Add dependencies from rustc dep-info output (stored in fingerprint directory) let dep_info_loc = fingerprint::dep_info_loc(context, unit); if let Some(paths) = fingerprint::parse_dep_info(unit.pkg, &dep_info_loc)? { @@ -43,9 +43,9 @@ fn add_deps_for_unit<'a, 'b>( } } else { debug!( - "can't find dep_info for {:?} {:?}", + "can't find dep_info for {:?} {}", unit.pkg.package_id(), - unit.profile + unit.target ); return Err(internal("dep_info missing")); } diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index c9658a8aa..406c969aa 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -170,6 +170,9 @@ features! { // Whether a lock file is published with this crate [unstable] publish_lockfile: bool, + + // Overriding profiles for dependencies. + [unstable] profile_overrides: bool, } } diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index b4b50fc90..da7269c5f 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -10,6 +10,7 @@ use toml; use url::Url; use core::{Dependency, PackageId, PackageIdSpec, SourceId, Summary}; +use core::profiles::Profiles; use core::{Edition, Feature, Features, WorkspaceConfig}; use core::interning::InternedString; use util::Config; @@ -152,59 +153,6 @@ impl ser::Serialize for TargetKind { } } -// Note that most of the fields here are skipped when serializing because we -// don't want to export them just yet (becomes a public API of Cargo). Others -// though are definitely needed! -#[derive(Clone, PartialEq, Eq, Debug, Hash, Serialize)] -pub struct Profile { - pub opt_level: String, - #[serde(skip_serializing)] - pub lto: Lto, - #[serde(skip_serializing)] - pub codegen_units: Option, // None = use rustc default - #[serde(skip_serializing)] - pub rustc_args: Option>, - #[serde(skip_serializing)] - pub rustdoc_args: Option>, - pub debuginfo: Option, - pub debug_assertions: bool, - pub overflow_checks: bool, - #[serde(skip_serializing)] - pub rpath: bool, - pub test: bool, - #[serde(skip_serializing)] - pub doc: bool, - #[serde(skip_serializing)] - pub run_custom_build: bool, - #[serde(skip_serializing)] - pub check: bool, - #[serde(skip_serializing)] - pub panic: Option, - #[serde(skip_serializing)] - pub incremental: bool, -} - -#[derive(Clone, PartialEq, Eq, Debug, Hash)] -pub enum Lto { - Bool(bool), - Named(String), -} - -#[derive(Default, Clone, Debug, PartialEq, Eq)] -pub struct Profiles { - pub release: Profile, - pub dev: Profile, - pub test: Profile, - pub test_deps: Profile, - pub bench: Profile, - pub bench_deps: Profile, - pub doc: Profile, - pub custom_build: Profile, - pub check: Profile, - pub check_test: Profile, - pub doctest: Profile, -} - /// Information about a binary, a library, an example, etc. that is part of the /// package. #[derive(Clone, Hash, PartialEq, Eq, Debug)] @@ -726,112 +674,3 @@ impl fmt::Display for Target { } } } - -impl Profile { - pub fn default_dev() -> Profile { - Profile { - debuginfo: Some(2), - debug_assertions: true, - overflow_checks: true, - incremental: true, - ..Profile::default() - } - } - - pub fn default_release() -> Profile { - Profile { - opt_level: "3".to_string(), - debuginfo: None, - ..Profile::default() - } - } - - pub fn default_test() -> Profile { - Profile { - test: true, - ..Profile::default_dev() - } - } - - pub fn default_bench() -> Profile { - Profile { - test: true, - ..Profile::default_release() - } - } - - pub fn default_doc() -> Profile { - Profile { - doc: true, - ..Profile::default_dev() - } - } - - pub fn default_custom_build() -> Profile { - Profile { - run_custom_build: true, - ..Profile::default_dev() - } - } - - pub fn default_check() -> Profile { - Profile { - check: true, - ..Profile::default_dev() - } - } - - pub fn default_check_test() -> Profile { - Profile { - check: true, - test: true, - ..Profile::default_dev() - } - } - - pub fn default_doctest() -> Profile { - Profile { - doc: true, - test: true, - ..Profile::default_dev() - } - } -} - -impl Default for Profile { - fn default() -> Profile { - Profile { - opt_level: "0".to_string(), - lto: Lto::Bool(false), - codegen_units: None, - rustc_args: None, - rustdoc_args: None, - debuginfo: None, - debug_assertions: false, - overflow_checks: false, - rpath: false, - test: false, - doc: false, - run_custom_build: false, - check: false, - panic: None, - incremental: false, - } - } -} - -impl fmt::Display for Profile { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.test { - write!(f, "Profile(test)") - } else if self.doc { - write!(f, "Profile(doc)") - } else if self.run_custom_build { - write!(f, "Profile(run)") - } else if self.check { - write!(f, "Profile(check)") - } else { - write!(f, "Profile(build)") - } - } -} diff --git a/src/cargo/core/mod.rs b/src/cargo/core/mod.rs index d112050d0..347772bb8 100644 --- a/src/cargo/core/mod.rs +++ b/src/cargo/core/mod.rs @@ -1,7 +1,7 @@ pub use self::dependency::Dependency; pub use self::features::{CliUnstable, Edition, Feature, Features}; pub use self::manifest::{EitherManifest, VirtualManifest}; -pub use self::manifest::{LibKind, Manifest, Profile, Profiles, Target, TargetKind}; +pub use self::manifest::{LibKind, Manifest, Target, TargetKind}; pub use self::package::{Package, PackageSet}; pub use self::package_id::PackageId; pub use self::package_id_spec::PackageIdSpec; @@ -22,6 +22,7 @@ pub mod summary; pub mod shell; pub mod registry; pub mod compiler; +pub mod profiles; mod interning; mod package_id_spec; mod workspace; diff --git a/src/cargo/core/profiles.rs b/src/cargo/core/profiles.rs new file mode 100644 index 000000000..615b0ac54 --- /dev/null +++ b/src/cargo/core/profiles.rs @@ -0,0 +1,394 @@ +use std::collections::HashMap; +use std::{cmp, fmt, hash}; +use ops::CompileMode; +use util::toml::{StringOrBool, TomlProfile, U32OrBool}; +use core::compiler::Context; +use core::compiler::Unit; +use core::interning::InternedString; + +/// Collection of all user profiles. +#[derive(Clone, Debug)] +pub struct Profiles { + dev: ProfileMaker, + release: ProfileMaker, + test: ProfileMaker, + bench: ProfileMaker, + doc: ProfileMaker, +} + +impl Profiles { + pub fn new( + dev: Option, + release: Option, + test: Option, + bench: Option, + doc: Option, + ) -> Profiles { + Profiles { + dev: ProfileMaker { + default: Profile::default_dev(), + toml: dev, + }, + release: ProfileMaker { + default: Profile::default_release(), + toml: release, + }, + test: ProfileMaker { + default: Profile::default_test(), + toml: test, + }, + bench: ProfileMaker { + default: Profile::default_bench(), + toml: bench, + }, + doc: ProfileMaker { + default: Profile::default_doc(), + toml: doc, + }, + } + } + + /// Retrieve the profile for a target. + /// `is_member` is whether or not this package is a member of the + /// workspace. + fn get_profile( + &self, + pkg_name: &str, + is_member: bool, + profile_for: ProfileFor, + mode: CompileMode, + release: bool, + ) -> Profile { + let maker = match mode { + CompileMode::Test => { + if release { + &self.bench + } else { + &self.test + } + } + CompileMode::Build + | CompileMode::Check { .. } + | CompileMode::Doctest + | CompileMode::RunCustomBuild => { + // Note: RunCustomBuild doesn't normally use this code path. + // `build_unit_profiles` normally ensures that it selects the + // ancestor's profile. However `cargo clean -p` can hit this + // path. + // TODO: I think `cargo clean -p xxx` is not cleaning out + // the "OUT_DIR" directory. This is not a new bug. + if release { + &self.release + } else { + &self.dev + } + } + CompileMode::Bench => &self.bench, + CompileMode::Doc { .. } => &self.doc, + }; + let mut profile = maker.profile_for(pkg_name, is_member, profile_for); + // `panic` should not be set for tests/benches, or any of their + // dependencies. + if profile_for == ProfileFor::TestDependency || mode.is_any_test() { + profile.panic = None; + } + profile + } + + /// This returns a generic base profile. This is currently used for the + /// `[Finished]` line. It is not entirely accurate, since it doesn't + /// select for the package that was actually built. + pub fn base_profile(&self, release: bool) -> Profile { + if release { + self.release.profile_for("", true, ProfileFor::Any) + } else { + self.dev.profile_for("", true, ProfileFor::Any) + } + } + + /// Build a mapping from Unit -> Profile for all the given units and all + /// of their dependencies. + pub fn build_unit_profiles<'a, 'cfg>( + &self, + units: &[Unit<'a>], + cx: &Context<'a, 'cfg>, + ) -> HashMap, Profile> { + let mut result = HashMap::new(); + for unit in units.iter() { + self.build_unit_profiles_rec(unit, None, cx, &mut result); + } + result + } + + fn build_unit_profiles_rec<'a, 'cfg>( + &self, + unit: &Unit<'a>, + parent: Option<&Unit<'a>>, + cx: &Context<'a, 'cfg>, + map: &mut HashMap, Profile>, + ) { + if !map.contains_key(unit) { + let for_unit = if unit.mode.is_run_custom_build() { + // The profile for *running* a custom build script is the + // target the script is running for. This allows + // `custom_build::build_work` to set the correct environment + // settings. + // + // In the case of `cargo clean -p`, it creates artificial + // units to compute filenames, without a dependency hierarchy, + // so we don't have a parent here. That should be OK, it + // only affects the environment variables used to *run* + // `build.rs`. + parent.unwrap_or(unit) + } else { + unit + }; + let profile = self.get_profile( + &for_unit.pkg.name(), + cx.ws.is_member(for_unit.pkg), + for_unit.profile_for, + for_unit.mode, + cx.build_config.release, + ); + map.insert(*unit, profile); + let deps = cx.dep_targets(unit); + for dep in &deps { + self.build_unit_profiles_rec(dep, Some(unit), cx, map); + } + } + } +} + +/// An object used for handling the profile override hierarchy. +/// +/// The precedence of profiles are (first one wins): +/// - [profile.dev.overrides.name] - A named package. +/// - [profile.dev.overrides."*"] - This cannot apply to workspace members. +/// - [profile.dev.build_override] - This can only apply to `build.rs` scripts +/// and their dependencies. +/// - [profile.dev] +/// - Default (hard-coded) values. +#[derive(Debug, Clone)] +struct ProfileMaker { + default: Profile, + toml: Option, +} + +impl ProfileMaker { + fn profile_for(&self, pkg_name: &str, is_member: bool, profile_for: ProfileFor) -> Profile { + let mut profile = self.default.clone(); + if let Some(ref toml) = self.toml { + merge_profile(&mut profile, toml); + if profile_for == ProfileFor::CustomBuild { + if let Some(ref build_override) = toml.build_override { + merge_profile(&mut profile, build_override); + } + } + if let Some(ref overrides) = toml.overrides { + if !is_member { + if let Some(star) = overrides.get("*") { + merge_profile(&mut profile, star); + } + } + if let Some(byname) = overrides.get(pkg_name) { + merge_profile(&mut profile, byname); + } + } + } + profile + } +} + +fn merge_profile(profile: &mut Profile, toml: &TomlProfile) { + if let Some(ref opt_level) = toml.opt_level { + profile.opt_level = InternedString::new(&opt_level.0); + } + match toml.lto { + Some(StringOrBool::Bool(b)) => profile.lto = Lto::Bool(b), + Some(StringOrBool::String(ref n)) => profile.lto = Lto::Named(n.clone()), + None => {} + } + if toml.codegen_units.is_some() { + profile.codegen_units = toml.codegen_units; + } + match toml.debug { + Some(U32OrBool::U32(debug)) => profile.debuginfo = Some(debug), + Some(U32OrBool::Bool(true)) => profile.debuginfo = Some(2), + Some(U32OrBool::Bool(false)) => profile.debuginfo = None, + None => {} + } + if let Some(debug_assertions) = toml.debug_assertions { + profile.debug_assertions = debug_assertions; + } + if let Some(rpath) = toml.rpath { + profile.rpath = rpath; + } + if let Some(ref panic) = toml.panic { + profile.panic = Some(InternedString::new(panic)); + } + if let Some(overflow_checks) = toml.overflow_checks { + profile.overflow_checks = overflow_checks; + } + if let Some(incremental) = toml.incremental { + profile.incremental = incremental; + } +} + +/// Profile settings used to determine which compiler flags to use for a +/// target. +#[derive(Debug, Clone, Eq)] +pub struct Profile { + pub name: &'static str, + pub opt_level: InternedString, + pub lto: Lto, + // None = use rustc default + pub codegen_units: Option, + pub debuginfo: Option, + pub debug_assertions: bool, + pub overflow_checks: bool, + pub rpath: bool, + pub incremental: bool, + pub panic: Option, +} + +impl Default for Profile { + fn default() -> Profile { + Profile { + name: "", + opt_level: InternedString::new("0"), + lto: Lto::Bool(false), + codegen_units: None, + debuginfo: None, + debug_assertions: false, + overflow_checks: false, + rpath: false, + incremental: false, + panic: None, + } + } +} + +impl fmt::Display for Profile { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Profile({})", self.name) + } +} + +impl hash::Hash for Profile { + fn hash(&self, state: &mut H) + where + H: hash::Hasher, + { + self.comparable().hash(state); + } +} + +impl cmp::PartialEq for Profile { + fn eq(&self, other: &Self) -> bool { + self.comparable() == other.comparable() + } +} + +impl Profile { + fn default_dev() -> Profile { + Profile { + name: "dev", + debuginfo: Some(2), + debug_assertions: true, + overflow_checks: true, + incremental: true, + ..Profile::default() + } + } + + fn default_release() -> Profile { + Profile { + name: "release", + opt_level: InternedString::new("3"), + ..Profile::default() + } + } + + fn default_test() -> Profile { + Profile { + name: "test", + ..Profile::default_dev() + } + } + + fn default_bench() -> Profile { + Profile { + name: "bench", + ..Profile::default_release() + } + } + + fn default_doc() -> Profile { + Profile { + name: "doc", + ..Profile::default_dev() + } + } + + /// Compare all fields except `name`, which doesn't affect compilation. + /// This is necessary for `Unit` deduplication for things like "test" and + /// "dev" which are essentially the same. + fn comparable( + &self, + ) -> ( + &InternedString, + &Lto, + &Option, + &Option, + &bool, + &bool, + &bool, + &bool, + &Option, + ) { + ( + &self.opt_level, + &self.lto, + &self.codegen_units, + &self.debuginfo, + &self.debug_assertions, + &self.overflow_checks, + &self.rpath, + &self.incremental, + &self.panic, + ) + } +} + +/// The link-time-optimization setting. +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub enum Lto { + /// False = no LTO + /// True = "Fat" LTO + Bool(bool), + /// Named LTO settings like "thin". + Named(String), +} + +/// A flag used in `Unit` to indicate the purpose for the target. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub enum ProfileFor { + /// A general-purpose target. + Any, + /// A target for `build.rs` or any of its dependencies. This enables + /// `build_override` profiles for these targets. + CustomBuild, + /// A target that is a dependency of a test or benchmark. Currently this + /// enforces that the `panic` setting is not set. + TestDependency, +} + +impl ProfileFor { + pub fn all_values() -> Vec { + vec![ + ProfileFor::Any, + ProfileFor::CustomBuild, + ProfileFor::TestDependency, + ] + } +} diff --git a/src/cargo/core/workspace.rs b/src/cargo/core/workspace.rs index 72985eef3..0de6ef40a 100644 --- a/src/cargo/core/workspace.rs +++ b/src/cargo/core/workspace.rs @@ -8,8 +8,9 @@ use glob::glob; use url::Url; use core::registry::PackageRegistry; -use core::{Dependency, PackageIdSpec, Profile, Profiles}; +use core::{Dependency, PackageIdSpec}; use core::{EitherManifest, Package, SourceId, VirtualManifest}; +use core::profiles::Profiles; use ops; use sources::PathSource; use util::errors::{CargoResult, CargoResultExt}; @@ -307,6 +308,13 @@ impl<'cfg> Workspace<'cfg> { } } + /// Returns true if the package is a member of the workspace. + pub fn is_member(&self, pkg: &Package) -> bool { + // TODO: Implement this in a better way. + // Maybe make it part of Package? + self.members().any(|p| p == pkg) + } + pub fn is_ephemeral(&self) -> bool { self.is_ephemeral } @@ -645,24 +653,10 @@ impl<'cfg> Workspace<'cfg> { } if let Some(ref root_manifest) = self.root_manifest { - let default_profiles = Profiles { - release: Profile::default_release(), - dev: Profile::default_dev(), - test: Profile::default_test(), - test_deps: Profile::default_dev(), - bench: Profile::default_bench(), - bench_deps: Profile::default_release(), - doc: Profile::default_doc(), - custom_build: Profile::default_custom_build(), - check: Profile::default_check(), - check_test: Profile::default_check_test(), - doctest: Profile::default_doctest(), - }; - for pkg in self.members() .filter(|p| p.manifest_path() != root_manifest) { - if pkg.manifest().profiles() != &default_profiles { + if pkg.manifest().original().has_profiles() { let message = &format!( "profiles for the non root package will be ignored, \ specify profiles at the workspace root:\n\ diff --git a/src/cargo/ops/cargo_clean.rs b/src/cargo/ops/cargo_clean.rs index a8c388752..64eed3682 100644 --- a/src/cargo/ops/cargo_clean.rs +++ b/src/cargo/ops/cargo_clean.rs @@ -1,12 +1,14 @@ use std::fs; use std::path::Path; +use std::collections::HashMap; -use core::{Profiles, Workspace}; +use core::Workspace; use core::compiler::{BuildConfig, Context, Kind, Unit}; +use core::profiles::ProfileFor; use util::Config; use util::errors::{CargoResult, CargoResultExt}; use util::paths; -use ops; +use ops::{self, CompileMode}; pub struct CleanOptions<'a> { pub config: &'a Config, @@ -46,39 +48,16 @@ pub fn clean(ws: &Workspace, opts: &CleanOptions) -> CargoResult<()> { // Generate all relevant `Unit` targets for this package for target in pkg.targets() { for kind in [Kind::Host, Kind::Target].iter() { - let Profiles { - ref release, - ref dev, - ref test, - ref bench, - ref doc, - ref custom_build, - ref test_deps, - ref bench_deps, - ref check, - ref check_test, - ref doctest, - } = *profiles; - let profiles = [ - release, - dev, - test, - bench, - doc, - custom_build, - test_deps, - bench_deps, - check, - check_test, - doctest, - ]; - for profile in profiles.iter() { - units.push(Unit { - pkg, - target, - profile, - kind: *kind, - }); + for mode in CompileMode::all_modes() { + for profile_for in ProfileFor::all_values() { + units.push(Unit { + pkg, + target, + profile_for, + kind: *kind, + mode, + }); + } } } } @@ -86,13 +65,21 @@ pub fn clean(ws: &Workspace, opts: &CleanOptions) -> CargoResult<()> { let mut build_config = BuildConfig::new(config, Some(1), &opts.target, None)?; build_config.release = opts.release; - let mut cx = Context::new(ws, &resolve, &packages, opts.config, build_config, profiles)?; + let mut cx = Context::new( + ws, + &resolve, + &packages, + opts.config, + build_config, + profiles, + HashMap::new(), + )?; cx.prepare_units(None, &units)?; for unit in units.iter() { rm_rf(&cx.files().fingerprint_dir(unit), config)?; if unit.target.is_custom_build() { - if unit.profile.run_custom_build { + if unit.mode.is_run_custom_build() { rm_rf(&cx.files().build_script_out_dir(unit), config)?; } else { rm_rf(&cx.files().build_script_dir(unit), config)?; diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 85d58bd21..9850101b1 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -22,18 +22,19 @@ //! previously compiled dependency //! -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; use std::sync::Arc; -use core::{Package, Source, Target}; -use core::{PackageId, PackageIdSpec, Profile, Profiles, TargetKind, Workspace}; use core::compiler::{BuildConfig, Compilation, Context, DefaultExecutor, Executor}; use core::compiler::{Kind, Unit}; +use core::profiles::ProfileFor; use core::resolver::{Method, Resolve}; +use core::{Package, Source, Target}; +use core::{PackageId, PackageIdSpec, TargetKind, Workspace}; use ops; use util::config::Config; -use util::{profile, CargoResult, CargoResultExt}; +use util::{lev_distance, profile, CargoResult, CargoResultExt}; /// Contains information about how a package should be compiled. #[derive(Debug)] @@ -96,14 +97,84 @@ impl<'a> CompileOptions<'a> { } } -#[derive(Clone, Copy, PartialEq, Debug)] +/// The general "mode" of what to do. +/// This is used for two purposes. The commands themselves pass this in to +/// `compile_ws` to tell it the general execution strategy. This influences +/// the default targets selected. The other use is in the `Unit` struct +/// to indicate what is being done with a specific target. +#[derive(Clone, Copy, PartialEq, Debug, Eq, Hash)] pub enum CompileMode { + /// A target being built for a test. Test, + /// Building a target with `rustc` (lib or bin). Build, + /// Building a target with `rustc` to emit `rmeta` metadata only. If + /// `test` is true, then it is also compiled with `--test` to check it like + /// a test. Check { test: bool }, + /// A target being built for a benchmark. Bench, + /// A target that will be documented with `rustdoc`. + /// If `deps` is true, then it will also document all dependencies. Doc { deps: bool }, + /// A target that will be tested with `rustdoc`. Doctest, + /// A marker for Units that represent the execution of a `build.rs` + /// script. + RunCustomBuild, +} + +impl CompileMode { + /// Returns true if the unit is being checked. + pub fn is_check(&self) -> bool { + match *self { + CompileMode::Check { .. } => true, + _ => false, + } + } + + /// Returns true if this is a doc or doctest. Be careful using this. + /// Although both run rustdoc, the dependencies for those two modes are + /// very different. + pub fn is_doc(&self) -> bool { + match *self { + CompileMode::Doc { .. } | CompileMode::Doctest => true, + _ => false, + } + } + + /// Returns true if this is any type of test (test, benchmark, doctest, or + /// check-test). + pub fn is_any_test(&self) -> bool { + match *self { + CompileMode::Test + | CompileMode::Bench + | CompileMode::Check { test: true } + | CompileMode::Doctest => true, + _ => false, + } + } + + /// Returns true if this is the *execution* of a `build.rs` script. + pub fn is_run_custom_build(&self) -> bool { + *self == CompileMode::RunCustomBuild + } + + /// List of all modes (currently used by `cargo clean -p` for computing + /// all possible outputs). + pub fn all_modes() -> Vec { + vec![ + CompileMode::Test, + CompileMode::Build, + CompileMode::Check { test: true }, + CompileMode::Check { test: false }, + CompileMode::Bench, + CompileMode::Doc { deps: true }, + CompileMode::Doc { deps: false }, + CompileMode::Doctest, + CompileMode::RunCustomBuild, + ] + } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -267,13 +338,12 @@ pub fn compile_ws<'a>( .into_path_unlocked(); let mut build_config = BuildConfig::new(config, jobs, &target, Some(rustc_info_cache))?; build_config.release = release; - build_config.test = mode == CompileMode::Test || mode == CompileMode::Bench; build_config.json_messages = message_format == MessageFormat::Json; - if let CompileMode::Doc { deps } = mode { - build_config.doc_all = deps; - } - - let profiles = ws.profiles(); + let default_arch_kind = if build_config.requested_target.is_some() { + Kind::Target + } else { + Kind::Host + }; let specs = spec.into_package_id_specs(ws)?; let features = Method::split_features(features); @@ -296,62 +366,42 @@ pub fn compile_ws<'a>( }) .collect::>>()?; - let mut general_targets = Vec::new(); - let mut package_targets = Vec::new(); - - match (target_rustc_args, target_rustdoc_args) { - (&Some(..), _) | (_, &Some(..)) if to_builds.len() != 1 => { - panic!("`rustc` and `rustdoc` should not accept multiple `-p` flags") - } - (&Some(ref args), _) => { - let all_features = - resolve_all_features(&resolve_with_overrides, to_builds[0].package_id()); - let targets = - generate_targets(to_builds[0], profiles, mode, filter, &all_features, release)?; - if targets.len() == 1 { - let (target, profile) = targets[0]; - let mut profile = profile.clone(); - profile.rustc_args = Some(args.to_vec()); - general_targets.push((target, profile)); - } else { - bail!( - "extra arguments to `rustc` can only be passed to one \ - target, consider filtering\nthe package by passing \ - e.g. `--lib` or `--bin NAME` to specify a single target" - ) - } - } - (&None, &Some(ref args)) => { - let all_features = - resolve_all_features(&resolve_with_overrides, to_builds[0].package_id()); - let targets = - generate_targets(to_builds[0], profiles, mode, filter, &all_features, release)?; - if targets.len() == 1 { - let (target, profile) = targets[0]; - let mut profile = profile.clone(); - profile.rustdoc_args = Some(args.to_vec()); - general_targets.push((target, profile)); - } else { - bail!( - "extra arguments to `rustdoc` can only be passed to one \ - target, consider filtering\nthe package by passing e.g. \ - `--lib` or `--bin NAME` to specify a single target" - ) - } - } - (&None, &None) => for &to_build in to_builds.iter() { - let all_features = resolve_all_features(&resolve_with_overrides, to_build.package_id()); - let targets = - generate_targets(to_build, profiles, mode, filter, &all_features, release)?; - package_targets.push((to_build, targets)); - }, + let (extra_args, extra_args_name) = match (target_rustc_args, target_rustdoc_args) { + (&Some(ref args), _) => (Some(args.clone()), "rustc"), + (_, &Some(ref args)) => (Some(args.clone()), "rustdoc"), + _ => (None, ""), }; - for &(target, ref profile) in &general_targets { - for &to_build in to_builds.iter() { - package_targets.push((to_build, vec![(target, profile)])); + if extra_args.is_some() && to_builds.len() != 1 { + panic!( + "`{}` should not accept multiple `-p` flags", + extra_args_name + ); + } + + let profiles = ws.profiles(); + let mut extra_compiler_args = HashMap::new(); + + let units = generate_targets( + &to_builds, + filter, + default_arch_kind, + mode, + &resolve_with_overrides, + )?; + + if let Some(args) = extra_args { + if units.len() != 1 { + bail!( + "extra arguments to `{}` can only be passed to one \ + target, consider filtering\nthe package by passing \ + e.g. `--lib` or `--bin NAME` to specify a single target", + extra_args_name + ); } + extra_compiler_args.insert(units[0], args); } + let mut ret = { let _p = profile::start("compiling"); let mut cx = Context::new( @@ -361,50 +411,14 @@ pub fn compile_ws<'a>( config, build_config, profiles, + extra_compiler_args, )?; - let units = package_targets - .iter() - .flat_map(|&(pkg, ref targets)| { - let default_kind = if cx.build_config.requested_target.is_some() { - Kind::Target - } else { - Kind::Host - }; - targets.iter().map(move |&(target, profile)| Unit { - pkg, - target, - profile, - kind: if target.for_host() { - Kind::Host - } else { - default_kind - }, - }) - }) - .collect::>(); cx.compile(&units, export_dir.clone(), &exec)? }; ret.to_doc_test = to_builds.into_iter().cloned().collect(); return Ok(ret); - - fn resolve_all_features( - resolve_with_overrides: &Resolve, - package_id: &PackageId, - ) -> HashSet { - let mut features = resolve_with_overrides.features(package_id).clone(); - - // Include features enabled for use by dependencies so targets can also use them with the - // required-features field when deciding whether to be built or skipped. - for (dep, _) in resolve_with_overrides.deps(package_id) { - for feature in resolve_with_overrides.features(dep) { - features.insert(dep.name().to_string() + "/" + feature); - } - } - - features - } } impl FilterRule { @@ -496,6 +510,7 @@ impl CompileFilter { .. } => examples.is_specific() || tests.is_specific() || benches.is_specific(), }, + CompileMode::RunCustomBuild => panic!("Invalid mode"), } } @@ -533,307 +548,307 @@ impl CompileFilter { } } -#[derive(Clone, Copy, Debug)] -struct BuildProposal<'a> { - target: &'a Target, - profile: &'a Profile, - required: bool, -} - -fn generate_default_targets<'a>( +/// Generates all the base targets for the packages the user has requested to +/// compile. Dependencies for these targets are computed later in +/// `unit_dependencies`. +fn generate_targets<'a>( + packages: &[&'a Package], + filter: &CompileFilter, + default_arch_kind: Kind, mode: CompileMode, - targets: &'a [Target], - profile: &'a Profile, - dep: &'a Profile, - required_features_filterable: bool, -) -> Vec> { - match mode { - CompileMode::Bench => targets - .iter() - .filter(|t| t.benched()) - .map(|t| BuildProposal { - target: t, - profile, - required: !required_features_filterable, - }) - .collect::>(), - CompileMode::Test => { - let mut base = targets - .iter() - .filter(|t| t.tested()) - .map(|t| BuildProposal { - target: t, - profile: if t.is_example() { dep } else { profile }, - required: !required_features_filterable, - }) - .collect::>(); - - // Always compile the library if we're testing everything as - // it'll be needed for doctests - if let Some(t) = targets.iter().find(|t| t.is_lib()) { - if t.doctested() { - base.push(BuildProposal { - target: t, - profile: dep, - required: !required_features_filterable, - }); + resolve: &Resolve, +) -> CargoResult>> { + let mut units = Vec::new(); + + // Helper for creating a Unit struct. + let new_unit = + |pkg: &'a Package, target: &'a Target, mode: CompileMode, profile_for: ProfileFor| { + let actual_profile_for = if profile_for != ProfileFor::Any { + profile_for + } else if mode.is_any_test() { + // Force dependencies of this unit to not set `panic`. + ProfileFor::TestDependency + } else { + profile_for + }; + let actual_mode = match mode { + CompileMode::Test => { + if target.is_example() { + // Examples are included as regular binaries to verify + // that they compile. + CompileMode::Build + } else { + CompileMode::Test + } } + CompileMode::Build => match *target.kind() { + TargetKind::Test => CompileMode::Test, + TargetKind::Bench => CompileMode::Bench, + _ => CompileMode::Build, + }, + _ => mode, + }; + let kind = if target.for_host() { + Kind::Host + } else { + default_arch_kind + }; + Unit { + pkg, + target, + profile_for: actual_profile_for, + kind, + mode: actual_mode, } - base - } - CompileMode::Build | CompileMode::Check { .. } => targets - .iter() - .filter(|t| t.is_bin() || t.is_lib()) - .map(|t| BuildProposal { - target: t, - profile, - required: !required_features_filterable, - }) - .collect(), - CompileMode::Doc { .. } => targets - .iter() - .filter(|t| { - t.documented() - && (!t.is_bin() || !targets.iter().any(|l| l.is_lib() && l.name() == t.name())) - }) - .map(|t| BuildProposal { - target: t, - profile, - required: !required_features_filterable, - }) - .collect(), - CompileMode::Doctest => { - if let Some(t) = targets.iter().find(|t| t.is_lib()) { - if t.doctested() { - return vec![ - BuildProposal { - target: t, - profile, - required: !required_features_filterable, - }, - ]; + }; + + for pkg in packages { + let features = resolve_all_features(resolve, pkg.package_id()); + // Create a list of proposed targets. The `bool` value indicates + // whether or not all required features *must* be present. If false, + // and the features are not available, then it will be silently + // skipped. Generally, targets specified by name (`--bin foo`) are + // required, all others can be silently skipped if features are + // missing. + let mut proposals: Vec<(Unit<'a>, bool)> = Vec::new(); + + match *filter { + CompileFilter::Default { + required_features_filterable, + } => { + let default_units = generate_default_targets(pkg.targets(), mode) + .iter() + .map(|t| { + ( + new_unit(pkg, t, mode, ProfileFor::Any), + !required_features_filterable, + ) + }) + .collect::>(); + proposals.extend(default_units); + if mode == CompileMode::Test { + // Include the lib as it will be required for doctests. + if let Some(t) = pkg.targets().iter().find(|t| t.is_lib() && t.doctested()) { + proposals.push(( + new_unit(pkg, t, CompileMode::Build, ProfileFor::TestDependency), + false, + )); + } } } + CompileFilter::Only { + all_targets, + lib, + ref bins, + ref examples, + ref tests, + ref benches, + } => { + if lib { + if let Some(target) = pkg.targets().iter().find(|t| t.is_lib()) { + proposals.push((new_unit(pkg, target, mode, ProfileFor::Any), false)); + } else if !all_targets { + bail!("no library targets found") + } + } + // If --tests was specified, add all targets that would be + // generated by `cargo test`. + let test_filter = match *tests { + FilterRule::All => Target::tested, + FilterRule::Just(_) => Target::is_test, + }; + let test_mode = match mode { + CompileMode::Build => CompileMode::Test, + CompileMode::Check { .. } => CompileMode::Check { test: true }, + _ => mode, + }; + // If --benches was specified, add all targets that would be + // generated by `cargo bench`. + let bench_filter = match *benches { + FilterRule::All => Target::benched, + FilterRule::Just(_) => Target::is_bench, + }; + let bench_mode = match mode { + CompileMode::Build => CompileMode::Bench, + CompileMode::Check { .. } => CompileMode::Check { test: true }, + _ => mode, + }; - Vec::new() + proposals.extend( + list_rule_targets(pkg, bins, "bin", Target::is_bin)? + .into_iter() + .map(|(t, required)| (new_unit(pkg, t, mode, ProfileFor::Any), required)) + .chain( + list_rule_targets(pkg, examples, "example", Target::is_example)? + .into_iter() + .map(|(t, required)| { + (new_unit(pkg, t, mode, ProfileFor::Any), required) + }), + ) + .chain( + list_rule_targets(pkg, tests, "test", test_filter)? + .into_iter() + .map(|(t, required)| { + (new_unit(pkg, t, test_mode, ProfileFor::Any), required) + }), + ) + .chain( + list_rule_targets(pkg, benches, "bench", bench_filter)? + .into_iter() + .map(|(t, required)| { + (new_unit(pkg, t, bench_mode, ProfileFor::Any), required) + }), + ) + .collect::>(), + ); + } } - } -} -/// Given a filter rule and some context, propose a list of targets -fn propose_indicated_targets<'a>( - pkg: &'a Package, - rule: &FilterRule, - desc: &'static str, - is_expected_kind: fn(&Target) -> bool, - profile: &'a Profile, -) -> CargoResult>> { - match *rule { - FilterRule::All => { - let result = pkg.targets() - .iter() - .filter(|t| is_expected_kind(t)) - .map(|t| BuildProposal { - target: t, - profile, - required: false, - }); - Ok(result.collect()) + // If any integration tests/benches are being tested, make sure that + // binaries are built as well. + if !mode.is_check() && proposals.iter().any(|&(ref unit, _)| { + unit.mode.is_any_test() && (unit.target.is_test() || unit.target.is_bench()) + }) { + proposals.extend( + pkg.targets() + .iter() + .filter(|t| t.is_bin()) + .map(|t| (new_unit(pkg, t, CompileMode::Build, ProfileFor::Any), false)), + ); } - FilterRule::Just(ref names) => { - let mut targets = Vec::new(); - for name in names { - let target = pkg.targets() + + // Only include targets that are libraries or have all required + // features available. + for (unit, required) in proposals { + let unavailable_features = match unit.target.required_features() { + Some(rf) => rf.iter().filter(|f| !features.contains(*f)).collect(), + None => Vec::new(), + }; + if unit.target.is_lib() || unavailable_features.is_empty() { + units.push(unit); + } else if required { + let required_features = unit.target.required_features().unwrap(); + let quoted_required_features: Vec = required_features .iter() - .find(|t| t.name() == *name && is_expected_kind(t)); - let t = match target { - Some(t) => t, - None => { - let suggestion = pkg.find_closest_target(name, is_expected_kind); - match suggestion { - Some(s) => { - let suggested_name = s.name(); - bail!( - "no {} target named `{}`\n\nDid you mean `{}`?", - desc, - name, - suggested_name - ) - } - None => bail!("no {} target named `{}`", desc, name), - } - } - }; - debug!("found {} `{}`", desc, name); - targets.push(BuildProposal { - target: t, - profile, - required: true, - }); + .map(|s| format!("`{}`", s)) + .collect(); + bail!( + "target `{}` requires the features: {}\n\ + Consider enabling them by passing e.g. `--features=\"{}\"`", + unit.target.name(), + quoted_required_features.join(", "), + required_features.join(" ") + ); } - Ok(targets) + // else, silently skip target. } } + Ok(units) } -/// Collect the targets that are libraries or have all required features available. -fn filter_compatible_targets<'a>( - mut proposals: Vec>, - features: &HashSet, -) -> CargoResult> { - let mut compatible = Vec::with_capacity(proposals.len()); - for proposal in proposals.drain(..) { - let unavailable_features = match proposal.target.required_features() { - Some(rf) => rf.iter().filter(|f| !features.contains(*f)).collect(), - None => Vec::new(), - }; - if proposal.target.is_lib() || unavailable_features.is_empty() { - compatible.push((proposal.target, proposal.profile)); - } else if proposal.required { - let required_features = proposal.target.required_features().unwrap(); - let quoted_required_features: Vec = required_features +fn resolve_all_features( + resolve_with_overrides: &Resolve, + package_id: &PackageId, +) -> HashSet { + let mut features = resolve_with_overrides.features(package_id).clone(); + + // Include features enabled for use by dependencies so targets can also use them with the + // required-features field when deciding whether to be built or skipped. + for (dep, _) in resolve_with_overrides.deps(package_id) { + for feature in resolve_with_overrides.features(dep) { + features.insert(dep.name().to_string() + "/" + feature); + } + } + + features +} + +/// Given a list of all targets for a package, filters out only the targets +/// that are automatically included when the user doesn't specify any targets. +fn generate_default_targets(targets: &[Target], mode: CompileMode) -> Vec<&Target> { + match mode { + CompileMode::Bench => targets.iter().filter(|t| t.benched()).collect(), + CompileMode::Test => targets.iter().filter(|t| t.tested()).collect(), + CompileMode::Build | CompileMode::Check { .. } => targets + .iter() + .filter(|t| t.is_bin() || t.is_lib()) + .collect(), + CompileMode::Doc { .. } => { + // `doc` does lib and bins (bin with same name as lib is skipped). + targets .iter() - .map(|s| format!("`{}`", s)) - .collect(); - bail!( - "target `{}` requires the features: {}\n\ - Consider enabling them by passing e.g. `--features=\"{}\"`", - proposal.target.name(), - quoted_required_features.join(", "), - required_features.join(" ") - ); + .filter(|t| { + t.documented() + && (!t.is_bin() + || !targets.iter().any(|l| l.is_lib() && l.name() == t.name())) + }) + .collect() } + CompileMode::Doctest => { + // `test --doc`` + targets + .iter() + .find(|t| t.is_lib() && t.doctested()) + .into_iter() + .collect() + } + CompileMode::RunCustomBuild => panic!("Invalid mode"), } - Ok(compatible) } -/// Given the configuration for a build, this function will generate all -/// target/profile combinations needed to be built. -fn generate_targets<'a>( +/// Returns a list of targets based on command-line target selection flags. +/// The return value is a list of `(Target, bool)` pairs. The `bool` value +/// indicates whether or not all required features *must* be present. +fn list_rule_targets<'a>( pkg: &'a Package, - profiles: &'a Profiles, - mode: CompileMode, - filter: &CompileFilter, - features: &HashSet, - release: bool, -) -> CargoResult> { - let build = if release { - &profiles.release - } else { - &profiles.dev - }; - let test = if release { - &profiles.bench - } else { - &profiles.test - }; - let profile = match mode { - CompileMode::Test => test, - CompileMode::Bench => &profiles.bench, - CompileMode::Build => build, - CompileMode::Check { test: false } => &profiles.check, - CompileMode::Check { test: true } => &profiles.check_test, - CompileMode::Doc { .. } => &profiles.doc, - CompileMode::Doctest => &profiles.doctest, - }; - - let test_profile = if profile.check { - &profiles.check_test - } else if mode == CompileMode::Build { - test - } else { - profile - }; - - let bench_profile = if profile.check { - &profiles.check_test - } else if mode == CompileMode::Build { - &profiles.bench - } else { - profile - }; + rule: &FilterRule, + target_desc: &'static str, + is_expected_kind: fn(&Target) -> bool, +) -> CargoResult> { + match *rule { + FilterRule::All => Ok(pkg.targets() + .iter() + .filter(|t| is_expected_kind(t)) + .map(|t| (t, false)) + .collect()), + FilterRule::Just(ref names) => names + .iter() + .map(|name| find_target(pkg, name, target_desc, is_expected_kind)) + .collect(), + } +} - let targets = match *filter { - CompileFilter::Default { - required_features_filterable, - } => { - let deps = if release { - &profiles.bench_deps - } else { - &profiles.test_deps - }; - generate_default_targets( - mode, - pkg.targets(), - profile, - deps, - required_features_filterable, - ) - } - CompileFilter::Only { - all_targets, - lib, - ref bins, - ref examples, - ref tests, - ref benches, - } => { - let mut targets = Vec::new(); - - if lib { - if let Some(t) = pkg.targets().iter().find(|t| t.is_lib()) { - targets.push(BuildProposal { - target: t, - profile, - required: true, - }); - } else if !all_targets { - bail!("no library targets found") - } +/// Find the target for a specifically named target. +fn find_target<'a>( + pkg: &'a Package, + target_name: &str, + target_desc: &'static str, + is_expected_kind: fn(&Target) -> bool, +) -> CargoResult<(&'a Target, bool)> { + match pkg.targets() + .iter() + .find(|t| t.name() == target_name && is_expected_kind(t)) + { + // When a target is specified by name, required features *must* be + // available. + Some(t) => Ok((t, true)), + None => { + let suggestion = pkg.targets() + .iter() + .filter(|t| is_expected_kind(t)) + .map(|t| (lev_distance(target_name, t.name()), t)) + .filter(|&(d, _)| d < 4) + .min_by_key(|t| t.0) + .map(|t| t.1); + match suggestion { + Some(s) => bail!( + "no {} target named `{}`\n\nDid you mean `{}`?", + target_desc, + target_name, + s.name() + ), + None => bail!("no {} target named `{}`", target_desc, target_name), } - targets.append(&mut propose_indicated_targets( - pkg, - bins, - "bin", - Target::is_bin, - profile, - )?); - targets.append(&mut propose_indicated_targets( - pkg, - examples, - "example", - Target::is_example, - profile, - )?); - // If --tests was specified, add all targets that would be - // generated by `cargo test`. - let test_filter = match *tests { - FilterRule::All => Target::tested, - FilterRule::Just(_) => Target::is_test, - }; - targets.append(&mut propose_indicated_targets( - pkg, - tests, - "test", - test_filter, - test_profile, - )?); - // If --benches was specified, add all targets that would be - // generated by `cargo bench`. - let bench_filter = match *benches { - FilterRule::All => Target::benched, - FilterRule::Just(_) => Target::is_bench, - }; - targets.append(&mut propose_indicated_targets( - pkg, - benches, - "bench", - bench_filter, - bench_profile, - )?); - targets } - }; - - filter_compatible_targets(targets, features) + } } diff --git a/src/cargo/util/machine_message.rs b/src/cargo/util/machine_message.rs index d2225eacd..3104d4b60 100644 --- a/src/cargo/util/machine_message.rs +++ b/src/cargo/util/machine_message.rs @@ -1,7 +1,7 @@ use serde::ser; use serde_json::{self, Value}; -use core::{PackageId, Profile, Target}; +use core::{PackageId, Target}; pub trait Message: ser::Serialize { fn reason(&self) -> &str; @@ -30,7 +30,7 @@ impl<'a> Message for FromCompiler<'a> { pub struct Artifact<'a> { pub package_id: &'a PackageId, pub target: &'a Target, - pub profile: &'a Profile, + pub profile: ArtifactProfile, pub features: Vec, pub filenames: Vec, pub fresh: bool, @@ -42,6 +42,18 @@ impl<'a> Message for Artifact<'a> { } } +/// This is different from the regular `Profile` to maintain backwards +/// compatibility (in particular, `test` is no longer in `Profile`, but we +/// still want it to be included here). +#[derive(Serialize)] +pub struct ArtifactProfile { + pub opt_level: &'static str, + pub debuginfo: Option, + pub debug_assertions: bool, + pub overflow_checks: bool, + pub test: bool, +} + #[derive(Serialize)] pub struct BuildScript<'a> { pub package_id: &'a PackageId, diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 635e77493..34aad6e05 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -12,11 +12,12 @@ use serde_ignored; use toml; use url::Url; -use core::{GitReference, PackageIdSpec, Profiles, SourceId, WorkspaceConfig, WorkspaceRootConfig}; +use core::{GitReference, PackageIdSpec, SourceId, WorkspaceConfig, WorkspaceRootConfig}; use core::{Dependency, Manifest, PackageId, Summary, Target}; use core::{Edition, EitherManifest, Feature, Features, VirtualManifest}; use core::dependency::{Kind, Platform}; -use core::manifest::{LibKind, Lto, ManifestMetadata, Profile}; +use core::manifest::{LibKind, ManifestMetadata}; +use core::profiles::Profiles; use sources::CRATES_IO; use util::paths; use util::{self, Config, ToUrl}; @@ -243,8 +244,29 @@ pub struct TomlProfiles { release: Option, } +impl TomlProfiles { + fn validate(&self, features: &Features) -> CargoResult<()> { + if let Some(ref test) = self.test { + test.validate("test", features)?; + } + if let Some(ref doc) = self.doc { + doc.validate("doc", features)?; + } + if let Some(ref bench) = self.bench { + bench.validate("bench", features)?; + } + if let Some(ref dev) = self.dev { + dev.validate("dev", features)?; + } + if let Some(ref release) = self.release { + release.validate("release", features)?; + } + Ok(()) + } +} + #[derive(Clone, Debug)] -pub struct TomlOptLevel(String); +pub struct TomlOptLevel(pub String); impl<'de> de::Deserialize<'de> for TomlOptLevel { fn deserialize(d: D) -> Result @@ -347,20 +369,65 @@ impl<'de> de::Deserialize<'de> for U32OrBool { } #[derive(Deserialize, Serialize, Clone, Debug, Default)] +#[serde(rename_all = "kebab-case")] pub struct TomlProfile { - #[serde(rename = "opt-level")] - opt_level: Option, - lto: Option, - #[serde(rename = "codegen-units")] - codegen_units: Option, - debug: Option, - #[serde(rename = "debug-assertions")] - debug_assertions: Option, - rpath: Option, - panic: Option, - #[serde(rename = "overflow-checks")] - overflow_checks: Option, - incremental: Option, + pub opt_level: Option, + pub lto: Option, + pub codegen_units: Option, + pub debug: Option, + pub debug_assertions: Option, + pub rpath: Option, + pub panic: Option, + pub overflow_checks: Option, + pub incremental: Option, + pub overrides: Option>, + #[serde(rename = "build_override")] + pub build_override: Option>, +} + +impl TomlProfile { + fn validate(&self, name: &str, features: &Features) -> CargoResult<()> { + if let Some(ref profile) = self.build_override { + features.require(Feature::profile_overrides())?; + profile.validate_override()?; + } + if let Some(ref override_map) = self.overrides { + features.require(Feature::profile_overrides())?; + for profile in override_map.values() { + profile.validate_override()?; + } + } + + match name { + "dev" | "release" => {} + _ => { + if self.overrides.is_some() || self.build_override.is_some() { + bail!( + "Profile overrides may only be specified for `dev` + or `release` profile, not {}.", + name + ); + } + } + } + Ok(()) + } + + fn validate_override(&self) -> CargoResult<()> { + if self.overrides.is_some() || self.build_override.is_some() { + bail!("Profile overrides cannot be nested."); + } + if self.panic.is_some() { + bail!("`panic` may not be specified in a build override.") + } + if self.lto.is_some() { + bail!("`lto` may not be specified in a build override.") + } + if self.rpath.is_some() { + bail!("`rpath` may not be specified in a build override.") + } + Ok(()) + } } #[derive(Clone, Debug, Serialize)] @@ -794,6 +861,9 @@ impl TomlManifest { `[workspace]`, only one can be specified" ), }; + if let Some(ref profiles) = me.profile { + profiles.validate(&features)?; + } let profiles = build_profiles(&me.profile); let publish = match project.publish { Some(VecStringOrBool::VecString(ref vecstring)) => { @@ -1004,6 +1074,10 @@ impl TomlManifest { } } } + + pub fn has_profiles(&self) -> bool { + self.profile.is_some() + } } /// Will check a list of build targets, and make sure the target names are unique within a vector. @@ -1279,98 +1353,11 @@ impl fmt::Debug for PathValue { fn build_profiles(profiles: &Option) -> Profiles { let profiles = profiles.as_ref(); - let mut profiles = Profiles { - release: merge( - Profile::default_release(), - profiles.and_then(|p| p.release.as_ref()), - ), - dev: merge( - Profile::default_dev(), - profiles.and_then(|p| p.dev.as_ref()), - ), - test: merge( - Profile::default_test(), - profiles.and_then(|p| p.test.as_ref()), - ), - test_deps: merge( - Profile::default_dev(), - profiles.and_then(|p| p.dev.as_ref()), - ), - bench: merge( - Profile::default_bench(), - profiles.and_then(|p| p.bench.as_ref()), - ), - bench_deps: merge( - Profile::default_release(), - profiles.and_then(|p| p.release.as_ref()), - ), - doc: merge( - Profile::default_doc(), - profiles.and_then(|p| p.doc.as_ref()), - ), - custom_build: Profile::default_custom_build(), - check: merge( - Profile::default_check(), - profiles.and_then(|p| p.dev.as_ref()), - ), - check_test: merge( - Profile::default_check_test(), - profiles.and_then(|p| p.dev.as_ref()), - ), - doctest: Profile::default_doctest(), - }; - // The test/bench targets cannot have panic=abort because they'll all get - // compiled with --test which requires the unwind runtime currently - profiles.test.panic = None; - profiles.bench.panic = None; - profiles.test_deps.panic = None; - profiles.bench_deps.panic = None; - return profiles; - - fn merge(profile: Profile, toml: Option<&TomlProfile>) -> Profile { - let &TomlProfile { - ref opt_level, - ref lto, - codegen_units, - ref debug, - debug_assertions, - rpath, - ref panic, - ref overflow_checks, - ref incremental, - } = match toml { - Some(toml) => toml, - None => return profile, - }; - let debug = match *debug { - Some(U32OrBool::U32(debug)) => Some(Some(debug)), - Some(U32OrBool::Bool(true)) => Some(Some(2)), - Some(U32OrBool::Bool(false)) => Some(None), - None => None, - }; - Profile { - opt_level: opt_level - .clone() - .unwrap_or(TomlOptLevel(profile.opt_level)) - .0, - lto: match *lto { - Some(StringOrBool::Bool(b)) => Lto::Bool(b), - Some(StringOrBool::String(ref n)) => Lto::Named(n.clone()), - None => profile.lto, - }, - codegen_units, - rustc_args: None, - rustdoc_args: None, - debuginfo: debug.unwrap_or(profile.debuginfo), - debug_assertions: debug_assertions.unwrap_or(profile.debug_assertions), - overflow_checks: overflow_checks.unwrap_or(profile.overflow_checks), - rpath: rpath.unwrap_or(profile.rpath), - test: profile.test, - doc: profile.doc, - run_custom_build: profile.run_custom_build, - check: profile.check, - panic: panic.clone().or(profile.panic), - incremental: incremental.unwrap_or(profile.incremental), - } - } + Profiles::new( + profiles.and_then(|p| p.dev.clone()), + profiles.and_then(|p| p.release.clone()), + profiles.and_then(|p| p.test.clone()), + profiles.and_then(|p| p.bench.clone()), + profiles.and_then(|p| p.doc.clone()), + ) } diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 1a305ba1f..bd8ccbdad 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -192,3 +192,38 @@ cargo-features = ["edition"] ... rust = "2018" ``` + + +### Profile Overrides +* Tracking Issue: [rust-lang/rust#48683](https://github.com/rust-lang/rust/issues/48683) +* RFC: [#2282](https://github.com/rust-lang/rfcs/blob/master/text/2282-profile-dependencies.md) + +Profiles can be overridden for specific packages and custom build scripts. +The general format looks like this: + +```toml +cargo-features = ["profile-overrides"] + +[package] +... + +[profile.dev] +opt-level = 0 +debug = true + +# the `image` crate will be compiled with -Copt-level=3 +[profile.dev.overrides.image] +opt-level = 3 + +# All dependencies (but not this crate itself) will be compiled +# with -Copt-level=2 . This includes build dependencies. +[profile.dev.overrides."*"] +opt-level = 2 + +# Build scripts and their dependencies will be compiled with -Copt-level=3 +# By default, build scripts use the same rules as the rest of the profile +[profile.dev.build_override] +opt-level = 3 +``` + +Overrides can only be specified for dev and release profiles. diff --git a/tests/testsuite/check.rs b/tests/testsuite/check.rs index 9c7097075..8e3667c2a 100644 --- a/tests/testsuite/check.rs +++ b/tests/testsuite/check.rs @@ -747,7 +747,7 @@ fn check_filters() { .with_stderr_contains("[..] --crate-name foo src[/]lib.rs [..] --test [..]") .with_stderr_contains("[..] --crate-name foo src[/]lib.rs --crate-type lib [..]") .with_stderr_contains("[..] --crate-name foo src[/]main.rs [..] --test [..]") - .with_stderr_contains("[..] --crate-name foo src[/]main.rs --crate-type bin [..]") + // .with_stderr_contains("[..] --crate-name foo src[/]main.rs --crate-type bin [..]") .with_stderr_contains("[..]unused_unit_lib[..]") .with_stderr_contains("[..]unused_unit_bin[..]") .with_stderr_contains("[..]unused_normal_lib[..]") @@ -757,6 +757,8 @@ fn check_filters() { .with_stderr_contains("[..]unused_unit_ex1[..]") .with_stderr_does_not_contain("[..]unused_normal_b1[..]") .with_stderr_does_not_contain("[..]unused_unit_b1[..]"), + // with_stderr_does_not_contain --crate-type lib + // with_stderr_does_not_contain --crate-type bin ); p.root().join("target").rm_rf(); assert_that( @@ -764,7 +766,7 @@ fn check_filters() { execs() .with_status(0) .with_stderr_contains("[..]unused_normal_lib[..]") - .with_stderr_contains("[..]unused_normal_bin[..]") + // .with_stderr_contains("[..]unused_normal_bin[..]") .with_stderr_contains("[..]unused_unit_t1[..]") .with_stderr_does_not_contain("[..]unused_unit_lib[..]") .with_stderr_does_not_contain("[..]unused_unit_bin[..]") diff --git a/tests/testsuite/doc.rs b/tests/testsuite/doc.rs index 82638b040..ac9d269d3 100644 --- a/tests/testsuite/doc.rs +++ b/tests/testsuite/doc.rs @@ -8,6 +8,7 @@ use cargotest::support::{execs, project, path2url}; use cargotest::support::registry::Package; use hamcrest::{assert_that, existing_dir, existing_file, is_not}; use cargo::util::ProcessError; +use glob::glob; #[test] fn simple() { @@ -163,6 +164,20 @@ fn doc_deps() { assert_that(&p.root().join("target/doc/foo/index.html"), existing_file()); assert_that(&p.root().join("target/doc/bar/index.html"), existing_file()); + // Verify that it only emits rmeta for the dependency. + assert_eq!( + glob(&p.root().join("target/debug/**/*.rlib").to_str().unwrap()) + .unwrap() + .count(), + 0 + ); + assert_eq!( + glob(&p.root().join("target/debug/deps/libbar-*.rmeta").to_str().unwrap()) + .unwrap() + .count(), + 1 + ); + assert_that( p.cargo("doc") .env("RUST_LOG", "cargo::ops::cargo_rustc::fingerprint"), diff --git a/tests/testsuite/profiles.rs b/tests/testsuite/profiles.rs index 999de518c..8b644055a 100644 --- a/tests/testsuite/profiles.rs +++ b/tests/testsuite/profiles.rs @@ -2,6 +2,7 @@ use std::env; use cargotest::is_nightly; use cargotest::support::{execs, project}; +use cargotest::ChannelChanger; use hamcrest::assert_that; #[test] @@ -340,3 +341,115 @@ fn profile_in_virtual_manifest_works() { ), ); } + +#[test] +fn dep_override_gated() { + let p = project("foo") + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [profile.dev.build_override] + opt-level = 3 + "#, + ) + .file("src/lib.rs", "") + .build(); + + assert_that( + p.cargo("build").masquerade_as_nightly_cargo(), + execs().with_status(101).with_stderr( + "\ +error: failed to parse manifest at `[..]` + +Caused by: + feature `profile-overrides` is required + +consider adding `cargo-features = [\"profile-overrides\"]` to the manifest +", + ), + ); + + let p = project("foo") + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [profile.dev.overrides."*"] + opt-level = 3 + "#, + ) + .file("src/lib.rs", "") + .build(); + + assert_that( + p.cargo("build").masquerade_as_nightly_cargo(), + execs().with_status(101).with_stderr( + "\ +error: failed to parse manifest at `[..]` + +Caused by: + feature `profile-overrides` is required + +consider adding `cargo-features = [\"profile-overrides\"]` to the manifest +", + ), + ); +} + +#[test] +fn dep_override_basic() { + let p = project("foo") + .file( + "Cargo.toml", + r#" + cargo-features = ["profile-overrides"] + + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies] + bar = {path = "bar"} + + [profile.dev.overrides.bar] + opt-level = 3 + "#, + ) + .file("src/lib.rs", "") + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.0.1" + authors = [] + "#, + ) + .file("bar/src/lib.rs", "") + .build(); + + assert_that( + p.cargo("build -v").masquerade_as_nightly_cargo(), + execs().with_status(0).with_stderr( +"[COMPILING] bar v0.0.1 ([..]) +[RUNNING] `rustc --crate-name bar [..] -C opt-level=3 [..]` +[COMPILING] foo v0.0.1 ([..]) +[RUNNING] `rustc --crate-name foo [..]` +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]", + ) + // TODO: does_not_contain does not support patterns! + // .with_stderr_does_not_contain("\ + // `rustc --crate-name bar[..]-C opt-level=3"), + ); + +} diff --git a/tests/testsuite/test.rs b/tests/testsuite/test.rs index a1953003f..19afe54cc 100644 --- a/tests/testsuite/test.rs +++ b/tests/testsuite/test.rs @@ -1649,6 +1649,7 @@ fn test_run_implicit_test_target() { ) .build(); + // TODO FIXME: This needs to better verify that examples are not built. assert_that( prj.cargo("test").arg("--tests"), execs() @@ -1658,8 +1659,7 @@ fn test_run_implicit_test_target() { [COMPILING] foo v0.0.1 ({dir}) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] [RUNNING] target[/]debug[/]deps[/]mybin-[..][EXE] -[RUNNING] target[/]debug[/]deps[/]mytest-[..][EXE] -[RUNNING] target[/]debug[/]examples[/]myexm-[..][EXE]", +[RUNNING] target[/]debug[/]deps[/]mytest-[..][EXE]", dir = prj.url() )) .with_stdout_contains("test test_in_test ... ok"), @@ -1742,13 +1742,13 @@ fn test_run_implicit_example_target() { ) .build(); + // TODO FIXME - verify example does NOT get run. assert_that( prj.cargo("test").arg("--examples"), execs().with_status(0).with_stderr(format!( "\ [COMPILING] foo v0.0.1 ({dir}) -[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] -[RUNNING] target[/]debug[/]examples[/]myexm-[..][EXE]", +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]", dir = prj.url() )), );